namespace Jsl.FxCop
{
    using System;
    using System.Collections.Generic;
    using System.Runtime.Serialization;
    using Microsoft.FxCop.Sdk;

    /// <summary>
    /// Holds component information gathered from the InitializeComponent method.
    /// </summary>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2240:ImplementISerializableCorrectly", Justification = "Doesn't need to be serializable.")]
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification = "By design.")]
    [Serializable]
    public sealed class ComponentInfo : Dictionary<string, object>
    {
        private readonly Node component;
        private SourceContext sourceContext;

        #region Constructors
        /// <summary>
        /// Initializes a new instance of the <see cref="ComponentInfo"/> class.
        /// </summary>
        /// <param name="component">The component.</param>
        public ComponentInfo(Node component)
        {
            this.component = component;
            this["Type"] = InstructionHelper.GetTypeNodeFromNode(component);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ComponentInfo"/> class.
        /// </summary>
        /// <param name="info">A <see cref="T:System.Runtime.Serialization.SerializationInfo"/> object containing the information required to serialize the <see cref="T:System.Collections.Generic.Dictionary`2"/>.</param>
        /// <param name="context">A <see cref="T:System.Runtime.Serialization.StreamingContext"/> structure containing the source and destination of the serialized stream associated with the <see cref="T:System.Collections.Generic.Dictionary`2"/>.</param>
        private ComponentInfo(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
        }
        #endregion Constructors

        #region Public Properties
        /// <summary>
        /// Gets the component.
        /// </summary>
        /// <value>The component.</value>
        public Node Component
        {
            get { return this.component; }
        }

        /// <summary>
        /// Gets the source context to use in rule resolutions.
        /// </summary>
        /// <value>The source context.</value>
        public SourceContext SourceContext
        {
            get { return this.sourceContext; }
        }

        /// <summary>
        /// Gets the type of the component.
        /// </summary>
        /// <value>The type of the component.</value>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods", Justification = "By design.")]
        public TypeNode Type
        {
            get
            {
                return InstructionHelper.GetTypeNodeFromNode(this.component);
            }
        }
        #endregion Public Properties

        #region Public Methods
        /// <summary>
        /// Gets the value.
        /// </summary>
        /// <typeparam name="TValue">The type of the value.</typeparam>
        /// <param name="key">The key to search for.</param>
        /// <returns>The value or the default value for the specified value type.</returns>
        /// <remarks>
        /// This is basically a short-cut for TryGetValue.
        /// </remarks>
        public TValue GetValue<TValue>(string key)
        {
            TValue value;
            this.TryGetValue<TValue>(key, out value);

            return value;
        }

        /// <summary>
        /// Gets the value.
        /// </summary>
        /// <typeparam name="TValue">The type of the value.</typeparam>
        /// <param name="key">The key to search for.</param>
        /// <param name="defaultValue">The default value.</param>
        /// <returns>The value or the default value for the specified value type.</returns>
        /// <remarks>
        /// This is basically a short-cut for TryGetValue.
        /// </remarks>
        public TValue GetValue<TValue>(string key, TValue defaultValue)
        {
            TValue value;
            if (!this.TryGetValue<TValue>(key, out value))
            {
                value = defaultValue;
            }

            return value;
        }

        /// <summary>
        /// Determines whether the parent is the specified parent.
        /// </summary>
        /// <param name="parent">The parent.</param>
        /// <returns><c>true</c> if the specified component has the specified parent.</returns>
        public bool HasParent(ComponentInfo parent)
        {
            Node actualParent;
            if (this.TryGetValue("Parent", out actualParent))
            {
                return object.ReferenceEquals(parent.Component, actualParent);
            }

            return false;
        }

        /// <summary>
        /// Sets the source context if it hasn't already been set.
        /// </summary>
        /// <param name="sourceContext">The source context.</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "sourceContext", Justification = "By design.")]
        public void SetSourceContext(SourceContext sourceContext)
        {
            if (this.sourceContext.StartLine == 0)
            {
                if (sourceContext.StartLine != 0)
                {
                    this.sourceContext = sourceContext;
                }
            }
        }

        /// <summary>
        /// Gets the value associated with the specified key.
        /// </summary>
        /// <typeparam name="TValue">The expected type of the value.</typeparam>
        /// <param name="key">The key of the value to get.</param>
        /// <param name="value">When this method returns, contains the value associated with the specified key, if the key
        /// is found; otherwise, the default value for the type of the value parameter. This parameter is passed uninitialized.</param>
        /// <returns><c>true</c> if the component contains an element with the specified key; otherwise, <c>false</c></returns>
        /// <exception cref="ArgumentNullException">key is null.</exception>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.StyleCop.CSharp.ReadabilityRules", "SA1100:DoNotPrefixCallsWithBaseUnlessLocalImplementationExists", Justification = "base is really required or the method will call itself.")]
        public bool TryGetValue<TValue>(string key, out TValue value)
        {
            // NOTE: base must be used to call the correct TryGetValue method.
            object tempValue;
            if (base.TryGetValue(key, out tempValue))
            {
                value = (TValue)tempValue;
                return true;
            }

            value = default(TValue);
            return false;
        }
        #endregion Public Methods
    }
}